package com.capinfo.commons.project.service.security.impl;

import java.io.UnsupportedEncodingException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.transaction.UnexpectedRollbackException;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import com.capinfo.commons.project.model.SystemConfig;
import com.capinfo.commons.project.model.security.SystemResource;
import com.capinfo.commons.project.model.security.SystemRole;
import com.capinfo.commons.project.model.security.SystemViewStamp;
import com.capinfo.commons.project.parameter.security.SystemResourceParameter;
import com.capinfo.commons.project.service.security.SystemResourceService;
import com.capinfo.components.treemenu.service.NodeElementExtracter;
import com.capinfo.components.treemenu.service.TreeManageService;
import com.capinfo.framework.dao.SearchCriteria.OrderRow;
import com.capinfo.framework.dao.SearchCriteriaBuilder;
import com.capinfo.framework.dao.impl.restriction.RestrictionExpression;
import com.capinfo.framework.exception.ObjectNotFoundException;
import com.capinfo.framework.service.impl.CommonsDataOperationServiceImpl;
import com.capinfo.framework.util.PropertyUtils;

@Transactional(transactionManager = "transactionManager4Ecomm", propagation = Propagation.REQUIRED)
public class SystemResourceServiceImpl extends CommonsDataOperationServiceImpl<SystemResource, SystemResourceParameter> implements SystemResourceService {
	private final String JSON_CACHE_KEY = "SECURITY_RESOURCE_JSON_CACHE";

	private TreeManageService<SystemResource> treeManageService;

	public SystemResource getResourceById(Long id) {
		
		try {
			return this.getGeneralService().getObjectById(SystemResource.class, id);
		} catch (ObjectNotFoundException e) {
			return null;
		}
	}

	public boolean validateResourceExists(SystemResourceParameter parameter) {
		
		SystemResource entity = parameter.getEntity();
		SearchCriteriaBuilder<SystemResource> searchCriteriaBuilder = new SearchCriteriaBuilder<SystemResource>(SystemResource.class);
		searchCriteriaBuilder.addQueryCondition("value", RestrictionExpression.EQUALS_OP, entity.getValue());
		if(entity.getId() != null){
			searchCriteriaBuilder.addQueryCondition("id", RestrictionExpression.NOT_EQUALS_OP, entity.getId());
		}
		
		return getGeneralService().getCount(searchCriteriaBuilder.build()) > 0 ? Boolean.TRUE : Boolean.FALSE ;
	}

	@Override
	@Transactional(readOnly = false, rollbackFor = Exception.class)
	public SystemResource saveOrUpdate(SystemResourceParameter parameter) {
		
		return getCacheParameter(parameter).getNewEntity();
	}

	@Transactional(readOnly = false, rollbackFor = Exception.class)
	protected CacheParameter getCacheParameter(SystemResourceParameter parameter) {

		CacheParameter cacheParameter = new CacheParameter();
		
		SystemResource newEntity = null;
		if (parameter.getEntity().getId() == null) {

			newEntity = parameter.getEntity();
			
			cacheParameter.setResourceValue(newEntity.getValue());
			
			newEntity.setPosition(this.calculateResourcePosition(parameter));
			
		}else{
			
			newEntity = this.getResourceById(parameter.getEntity().getId());
			
			cacheParameter.setResourceValue(parameter.getEntity().getValue());
			
			BeanUtils.copyProperties(parameter.getEntity(), newEntity, new String[] {"roles", "position", "children"});
		}
		
		Set<SystemRole> roles = getRolesByIds(fitIds(parameter.getRoleIds()));
		
		if( parameter.isAllSubResourceHasParentRole() ) {
			
			this.setAllSubReourceRoles(newEntity, roles);
			//this.generalService.saveOrUpdate(newEntity);
		} else {
			
			newEntity.setRoles(roles);
		}
		getGeneralService().saveOrUpdate(newEntity);
		if( null != newEntity.getViewStampId() ){
			
			cacheParameter.setOriginalViewStamp(getGeneralService().getObjectById(SystemViewStamp.class, newEntity.getViewStampId()));
		}
		
		cacheParameter.setNewEntity(newEntity);
		
		return cacheParameter;
	}

	protected class CacheParameter {

		private String resourceValue;
		private SystemResource newEntity;
		private SystemViewStamp originalViewStamp;
		
		public CacheParameter() {}

		public String getResourceValue() {
			return resourceValue;
		}

		public void setResourceValue(String resourceValue) {
			this.resourceValue = resourceValue;
		}

		public SystemResource getNewEntity() {
			return newEntity;
		}

		public void setNewEntity(SystemResource newEntity) {
			this.newEntity = newEntity;
		}

		public SystemViewStamp getOriginalViewStamp() {
			return originalViewStamp;
		}

		public void setOriginalViewStamp(SystemViewStamp originalViewStamp) {
			this.originalViewStamp = originalViewStamp;
		}
	}
	
	private Set<SystemRole> getRolesByIds(String ids) {
		Set<SystemRole> userRoles = new HashSet<SystemRole>();
		
		if(StringUtils.isNotBlank(ids)){
			
			SearchCriteriaBuilder<SystemRole> searchCriteriaBuilder = new SearchCriteriaBuilder<SystemRole>(SystemRole.class);
			searchCriteriaBuilder.addAdditionalRestrictionSql("ID in(" + ids + ")");
			List<SystemRole> roles = getGeneralService().getObjects(searchCriteriaBuilder.build());
			
			for (SystemRole role : roles) {
				userRoles.add(role);
			}
		}
		return userRoles;
	}
	
	private String fitIds(Long[] ids) {
		if(ids.length == 0)
			return "";
		
		StringBuffer sb = new StringBuffer();
		for (Long id : ids) {
			sb.append(id).append(",");
		}
		return sb.deleteCharAt(sb.length() - 1).toString();
	}

	@Transactional(readOnly = false, rollbackFor = Exception.class)
	public boolean delete(SystemResourceParameter parameter) {
		return this.deleteResource(parameter)!=null;
	}
	
	@Override
	public List<SystemResource> getCategoryMenus() {
		SearchCriteriaBuilder<SystemResource> searchCriteriaBuilder = new SearchCriteriaBuilder<SystemResource>(SystemResource.class);
		searchCriteriaBuilder.addQueryCondition("categoryMenu", RestrictionExpression.EQUALS_OP, Boolean.TRUE);
		searchCriteriaBuilder.addOrderCondition("position", OrderRow.ORDER_ASC);
		
		return getGeneralService().getObjects(searchCriteriaBuilder.build());
	}

	@Override
	public List<SystemResource> getDisplayChildren(Long parentId) {
		
		SearchCriteriaBuilder<SystemResource> searchCriteriaBuilder = new SearchCriteriaBuilder<SystemResource>(SystemResource.class);
		searchCriteriaBuilder.addQueryCondition("parentId", RestrictionExpression.EQUALS_OP, parentId);
		searchCriteriaBuilder.addQueryCondition("display", RestrictionExpression.EQUALS_OP, Boolean.TRUE);
		
		searchCriteriaBuilder.addOrderCondition("position", OrderRow.ORDER_ASC);
		
		return getGeneralService().getObjects(searchCriteriaBuilder.build());
	}

	protected SystemResource deleteResource(SystemResourceParameter parameter){
		SystemResource originalResource = this.getResourceById(parameter.getEntity().getId());
		
		try {
			
			getGeneralService().delete(originalResource);

			return originalResource;
		} catch (Exception e) {
			return null;
		}
	}

	public boolean move(SystemResourceParameter parameter) {

		SystemResource currentNode = this.getResourceById(parameter.getCurrentId());
		SystemResource targetNode =  this.getResourceById(parameter.getTargetId());
		
		this.getTreeManageService().move(currentNode, targetNode, parameter.getPosition());
		
		return true;
	}

	@Transactional(readOnly = true, propagation = Propagation.SUPPORTS, noRollbackFor = {UnexpectedRollbackException.class})
	public byte[] retrieveResource(SystemResourceParameter parameter) {
		
		try {
			SystemConfig config = getResourceConfig();
			if(null == config || StringUtils.isBlank(config.getValue())){
				
				return buildResourceJson().getBytes("UTF-8");
			}
			
			return config.getValue().getBytes("UTF-8");
		} catch (UnsupportedEncodingException e) {
			
			e.printStackTrace();
		}
		
		return new byte[0];
	}
	
	private SystemResource getRootResource() {
		
		return this.getResourceById((new SystemResource()).getRootId());
	}
	
	public String buildResourceJson() {
		
		SearchCriteriaBuilder<SystemResource> criteriaBuilder = new SearchCriteriaBuilder<SystemResource>(SystemResource.class);
		criteriaBuilder.addOrderCondition("position", OrderRow.ORDER_ASC);
		SystemResource root = this.getRootResource();
		
		return buildResource(root, criteriaBuilder);
		
	}

	@Transactional(readOnly = false, propagation = Propagation.SUPPORTS, noRollbackFor = {UnexpectedRollbackException.class})
	public boolean rebuildResourceToDataBase(SystemResourceParameter parameter) {

		String resource = buildResourceJson();
		SystemConfig config = getResourceConfig();
		if(null == config){
			config = new SystemConfig();
			config.setKey(JSON_CACHE_KEY);
		}
		config.setValue(resource);
		
		getGeneralService().saveOrUpdate(config);
		return true;
	}

	/**
	 * <p>描述: 计算位置</p>
	 * @param parameter
	 * @return
	 * @author: xuxianping
	 * @update:
	 */
	private int calculateResourcePosition(SystemResourceParameter parameter) {
		
		SearchCriteriaBuilder<SystemResource> criteriaBuilder = new SearchCriteriaBuilder<SystemResource>(SystemResource.class);
		criteriaBuilder.addQueryCondition("parentId", RestrictionExpression.EQUALS_OP, parameter.getEntity().getParentId());
		Number maxPosition = this.getGeneralService().getMax(criteriaBuilder.build(), "position");
		
		return maxPosition.intValue() + 1;
	}
	
	private void setAllSubReourceRoles(SystemResource resource,Set<SystemRole> roles){
		
		List<SystemResource> children = resource.getChildren();
		resource.setRoles(roles);
		getGeneralService().saveOrUpdate(resource);
		
		if(children!= null && children.size() != 0){
			for(SystemResource subResource : children){
				setAllSubReourceRoles(subResource, roles);
			}
		}
	}
	
	private SystemConfig getResourceConfig() {
		
		SearchCriteriaBuilder<SystemConfig> criteriaBuilder = new SearchCriteriaBuilder<SystemConfig>(SystemConfig.class);
		criteriaBuilder.addQueryCondition("key", RestrictionExpression.EQUALS_OP, JSON_CACHE_KEY);
		
		SystemConfig config;
		try {
			config = getGeneralService().getObjectByCriteria(criteriaBuilder.build());
		} catch (ObjectNotFoundException e) {
			config = null;
		}
		return config;
	}
	
	@SuppressWarnings("serial")
	private String buildResource(SystemResource resource, SearchCriteriaBuilder<SystemResource> criteriaBuilder) {
		
		Map<String, NodeElementExtracter> propertyNameMapping = new HashMap<String, NodeElementExtracter>();
		propertyNameMapping.put("name", new NodeElementExtracter());
		propertyNameMapping.put("display", new NodeElementExtracter());
		propertyNameMapping.put("parentId", new NodeElementExtracter());
		
		propertyNameMapping.put("url", new NodeElementExtracter() {
			
			public Object extract(Object node, String propertyName) {

				Object value = PropertyUtils.getProperty(node, "value");
				if( null != value ) {

					return value.toString().replaceAll("\\*", "");
				}
				
				return "";
			}
		});
		
		propertyNameMapping.put("viewInRightPanel", new NodeElementExtracter());
		propertyNameMapping.put("roleIdString", new NodeElementExtracter() {
			
			@SuppressWarnings("unchecked")
			public Object extract(Object node, String propertyName) {

				Object value = PropertyUtils.getProperty(node, "roles");
				if( null != value ) {
					Set<SystemRole> roles = (Set<SystemRole>) value;
					StringBuilder roleIdString = new StringBuilder(", ");
					for(SystemRole role: roles) {
						
						roleIdString.append(role.getId()).append(", ");
					}
					
					return roleIdString.toString();
				}
				
				return "";
			}
		});
		propertyNameMapping.put("topMenu", new NodeElementExtracter() {
			
			@SuppressWarnings("unchecked")
			public Object extract(Object node, String propertyName) {

				return ( new Long(1).equals(PropertyUtils.getProperty(node, "parentId")) );
			}
		});
		
		return treeManageService.buildResource(resource, criteriaBuilder
												, new HashMap<String, String>(){
													{
														put("identifier", "id");
														put("label", "name");
													}
												}
												, propertyNameMapping);
	}
	
	public TreeManageService<SystemResource> getTreeManageService() {
		return treeManageService;
	}

	public void setTreeManageService(TreeManageService<SystemResource> treeManageService) {
		this.treeManageService = treeManageService;
	}
}
